using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.ComponentModel.Design.Serialization;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Design;
using System.Globalization;
using System.Linq;
using System.Windows.Forms;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.WowAddonStudio.FrameXml.Components.Support;
using Microsoft.WowAddonStudio.FrameXml.Serialization;
using Microsoft.WowAddonStudio.FrameXml.Serialization.LayoutFrames;
using Microsoft.WowAddonStudio.FrameXml.Services;

using Color = System.Drawing.Color;
using Action = Microsoft.WowAddonStudio.FrameXml.Components.Actions.Action;

namespace Microsoft.WowAddonStudio.FrameXml.Components
{
    /// <summary>
    /// Provides the base class for all FrameXML controls.
    /// </summary>
    [DebuggerDisplay("SerializationObject = {SerializationObject}")]
    public partial class FrameXmlControl<TSerializationObject> : UserControl, IFrameXmlControl
        where TSerializationObject : SerializationObject, new()
    {
        private const string parentNameToken = "$parent";
		private SerializationObject serializationObject;

        /// <summary>
        /// Initializes a new instance of the <see cref="FrameXmlControl&lt;TSerializationObject&gt;"/> class.
        /// </summary>
		public FrameXmlControl()
        {
			InitializeFrameXmlComponent(null);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="FrameXmlControl&lt;TSerializationObject&gt;"/> class.
        /// </summary>
        /// <param name="serializationObject">The serialization object.</param>
        public FrameXmlControl(TSerializationObject serializationObject)
        {
			if (serializationObject == null)
				throw new ArgumentNullException("serializationObject");

        	InitializeFrameXmlComponent(serializationObject);
        }

        /// <summary>
        /// Initializes the frame XML component.
        /// </summary>
        /// <param name="serializationObject">The serialization object.</param>
		private void InitializeFrameXmlComponent(TSerializationObject serializationObject)
        {
            this.SerializationObject = serializationObject ?? SerializationObjectFactory.Default.CreateSerializationObject(this.SerializationObjectType);
        }

        /// <summary>
        /// Gets or sets the site of the control.
        /// </summary>
        /// <value></value>
        /// <returns>
        /// The <see cref="T:System.ComponentModel.ISite"/> associated with the <see cref="T:System.Windows.Forms.Control"/>, if any.
        /// </returns>
        public override ISite Site
        {
            set
            {
                base.Site = value;
                if (Site != null)
                    this.Initialize();
            }
        }

        /// <summary>
        /// Initializes this instance.
        /// </summary>
        protected virtual void Initialize()
        {
            this.InitializeStyles();
        }

        /// <summary>
		/// Initializes the styles.
		/// </summary>
        protected virtual void InitializeStyles()
        {
            // Set double-buffering, user paint and all painting in WM_PAINT
			this.SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint |
				ControlStyles.AllPaintingInWmPaint, true);
        	this.ResizeRedraw = true;

            // Enable transparent back color
            this.SetStyle(ControlStyles.SupportsTransparentBackColor, true);
        }

        /// <summary>
        /// Gets the type of the serialization object this control manages.
        /// </summary>
        public virtual Type SerializationObjectType
        {
            get { return typeof(TSerializationObject); }
        }

    	/// <summary>
        /// Gets or sets the serialization object the control visualizes.
        /// </summary>
        public virtual SerializationObject SerializationObject
    	{
    		get { return serializationObject; }
    		set
    		{
				if (serializationObject != value)
				{
					var notifyingObject = serializationObject as INotifyPropertyChanged;
					if (notifyingObject != null)
					{
						notifyingObject.PropertyChanged -= OnPropertyChanged;
					}

					serializationObject = value;

					notifyingObject = serializationObject as INotifyPropertyChanged;
					if (notifyingObject != null)
					{
						notifyingObject.PropertyChanged += OnPropertyChanged;
					}
				}
    		}
    	}

        /// <summary>
        /// Gets or sets the parent FrameXML component.
        /// </summary>
        IFrameXmlComponent IFrameXmlComponent.Parent
        {
            get
            {
                var parent = this.Parent as IFrameXmlComponent;
                if (parent == null)
                    throw new InvalidOperationException("Parent is not a FrameXML component.");

                return parent;
            }
            set
            {
                this.Parent = value == null ? null : value as Control;
            }
        }

        /// <summary>
        /// Gets or sets the parent FrameXML control.
        /// </summary>
        IFrameXmlControl IFrameXmlControl.Parent
        {
            get
            {
                var parent = this.Parent as IFrameXmlControl;
                if (parent == null)
                    throw new InvalidOperationException("Parent is not a FrameXML control.");

                return parent;
            }
            set
            {
                if (value == null)
                    this.Parent = null;
                else
                {
                    var parent = value as Control;
                    if (parent == null)
                        throw new ArgumentException("Parent is not a control.", "value");

                    this.Parent = parent;
                }
            }
        }

        /// <summary>
        /// Gets a value indicating whether this instance has parent.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance has parent; otherwise, <c>false</c>.
        /// </value>
        public bool HasParent
        {
            get { return this.Parent != null ? true : false; }
        }

		/// <summary>
		/// Gets or sets a value indicating whether this instance is inherited.
		/// </summary>
		/// <value>
		/// 	<c>true</c> if this instance is inherited; otherwise, <c>false</c>.
		/// </value>
		public bool IsInherited
		{
			get
			{
				var parent = Parent as LayoutFrameControl;
				if (parent != null)
				{
					var parentInheritableObject = parent.serializationObject as InheritableObject;
					if (parentInheritableObject != null)
					{
						return parentInheritableObject.IsInherited(SerializationObject as InheritableObject) || parent.IsInherited;
					}
				}
				return false;
			}
		}

        /// <summary>
        /// Gets the default size of a FrameXML control.
        /// </summary>
        protected override Size DefaultSize
        {
            get { return Size.Empty; }
        }

        /// <summary>
        /// Gets the background color of the FrameXML control.
        /// </summary>
        public override Color BackColor
        {
            get { return Color.Transparent; }
            set { base.BackColor = value; }
        }

        /// <summary>
        /// Gets a value indicating whether the control has actions.
        /// </summary>
        [Browsable(false)]
        public virtual bool HasActions
        {
            get { return GetActions().Length > 0; }
        }

        /// <summary>
        /// Gets the actions for the control.
        /// </summary>
        /// <returns>
        /// An array of <see cref="Action"/> instances.
        /// </returns>
        public virtual Action[] GetActions()
        {
            return new Action[0];
        }

        /// <summary>
        /// Expands a name by replacing the parent token with the name of the layout frame.
        /// </summary>
        /// <param name="name">The name to expand.</param>
        /// <returns>The expanded name.</returns>
        public string ExpandName(string name)
        {
            if (name == null)
                throw new ArgumentNullException("name");

            return name.Replace(parentNameToken, this.Name);
        }

        /// <summary>
        /// Expands the name of a named object.
        /// </summary>
        /// <param name="namedObject">The named object.</param>
        /// <returns>The expanded name.</returns>
        public string ExpandName(INamedObject namedObject)
        {
            if (namedObject == null)
                throw new ArgumentNullException("namedObject");
            if (namedObject.Name == null)
                throw new ArgumentException("Named object does not have a name.", "namedObject");

            return namedObject.Name != null ? this.ExpandName(namedObject.Name) : null;
        }

        /// <summary>
        /// Gets the child controls from the child hierarchy of the FrameXML contorl.
        /// </summary>
        /// <typeparam name="TControl">The type of controls to get.</typeparam>
        /// <returns>
        /// An enumerable collection of <typeparamref name="TControl"/> instances.
        /// </returns>
        public IEnumerable<TControl> GetChildControls<TControl>() where TControl : IFrameXmlControl
        {
            return this.Controls.OfType<TControl>()
                       .Concat(this.Controls.OfType<TControl>().SelectMany(child => child.GetChildControls<TControl>()));
        }

        /// <summary>
        /// Draws the FrameXML control.
        /// </summary>
        /// <param name="graphics">The <see cref="FrameXmlGraphics"/> context to draw to.</param>
        /// <param name="rectangle">The <see cref="Rectangle"/> to draw to.</param>
        public virtual bool DrawControl(FrameXmlGraphics graphics, Rectangle rectangle)
        {
            return false;
        }

        /// <summary>
        /// Draws a placeholder in place of the FrameXML control.
        /// </summary>
        /// <param name="graphics"></param>
        /// <param name="rectangle"></param>
        public virtual void DrawPlaceholder(FrameXmlGraphics graphics, Rectangle rectangle)
        {
        }

    	/// <summary>
        /// Gets the FrameXML image service.
        /// </summary>
        protected FrameXmlImageService ImageService
        {
            get { return this.GetService<FrameXmlImageService>(); }
        }

        /// <summary>
        /// Gets the FrameXML font service.
        /// </summary>
        protected FrameXmlFontService FontService
        {
            get { return this.GetService<FrameXmlFontService>(); }
        }

        /// <summary>
        /// Raises the <see cref="E:System.Windows.Forms.Control.ControlAdded"/> event.
        /// </summary>
        /// <param name="e">A <see cref="T:System.Windows.Forms.ControlEventArgs"/> that contains the event data.</param>
        protected override void OnControlAdded(ControlEventArgs e)
        {
            // Check that only FrameXML controls are added (ControlAdded is only raised afterwards)
            var frameXmlControl = e.Control as IFrameXmlControl;
            if (frameXmlControl == null)
                throw new ArgumentException("Only FrameXML controls can be added.");

            base.OnControlAdded(e);

            // If the associated serialization object is an ISerializationObjectContainer, add serialization object of the newly added control
            var container = this.SerializationObject as ISerializationObjectContainer;
            if (container != null)
            {
                // Check that the container does not contain the serialization object yet
				// and it is not inherited
				if (!container.Contains(frameXmlControl.SerializationObject) 
					&& !frameXmlControl.IsInherited)
                    container.Add(frameXmlControl.SerializationObject);
            }

            // Perform layout on newly added control
            this.PerformLayout(e.Control, "Parent");

			if (frameXmlControl.IsInherited)
				LockControl(frameXmlControl, true);

			ReInitVirtualControls();
        }

        /// <summary>
        /// Raises the <see cref="E:System.Windows.Forms.Control.ControlRemoved"/> event.
        /// </summary>
        /// <param name="e">A <see cref="T:System.Windows.Forms.ControlEventArgs"/> that contains the event data.</param>
        protected override void OnControlRemoved(ControlEventArgs e)
        {
            base.OnControlRemoved(e);

            var frameXmlControl = e.Control as IFrameXmlControl;

            // If the associated serialization object is an ISerializationObjectContainer, remove serialization object of the newly added control
            var container = this.SerializationObject as ISerializationObjectContainer;
            if (frameXmlControl != null && container != null)
                container.Remove(frameXmlControl.SerializationObject);

        	ReInitVirtualControls();

            // Sets the dirty flag.
            this.SetDirtyFlag();
        }

        /// <summary>
        /// Sets the dirty flag.
        /// </summary>
        protected void SetDirtyFlag()
        {
            var framXmlDesignService = ((IFrameXmlDesignerService) this.GetService(typeof (IFrameXmlDesignerService)));

            if(framXmlDesignService != null)
                framXmlDesignService.SetDirty(true);
        }

        /// <summary>
        /// Raises the <see cref="E:System.Windows.Forms.Control.Paint"/> event.
        /// </summary>
        /// <param name="e">A <see cref="T:System.Windows.Forms.PaintEventArgs"/> that contains the event data.</param>
        protected sealed override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
            this.OnPaint(new FrameXmlPaintEventArgs(new FrameXmlGraphics(this.ImageService, this.FontService, e.Graphics), e.ClipRectangle));
        }

        /// <summary>
        /// Paints the control.
        /// </summary>
        /// <param name="e">The <see cref="FrameXmlPaintEventArgs"/> containing the event data.</param>
        protected virtual void OnPaint(FrameXmlPaintEventArgs e)
        {
        }

		/// <summary>
		/// Reinits all inherited controls.
		/// </summary>
		private void ReInitVirtualControls()
		{
			//TODO: find inherited controls and reinitialize its child controls and fix implementation below
			//var inheritableObject = this.SerializationObject as InheritableObject;
			//if (inheritableObject != null && inheritableObject.Virtual)
			//{
			//    var frameXmlControls = Site.Container.Components.OfType<IFrameXmlControl>()
			//        .Where(control => (control.SerializationObject is InheritableObject
			//                           && ((InheritableObject) control.SerializationObject).Inherits == Name));
			//    if (frameXmlControls.Count() > 0)
			//    {
			//        var service = (FrameXmlComponentFactory)GetService(typeof(FrameXmlComponentFactory));
			//        foreach (var frameXmlControl in frameXmlControls)
			//        {
			//            service.ReinitializeComponent(frameXmlControl);
			//            NotifyPropertyValueUIItemsChanged();
			//        }
			//    }
			//}
		}

		/// <summary>
		/// Notifies the property value UI items changed.
		/// </summary>
		protected void NotifyPropertyValueUIItemsChanged()
		{
			var propertyValueUIService = GetService(typeof(IPropertyValueUIService)) as IPropertyValueUIService;
			if (propertyValueUIService != null)
				propertyValueUIService.NotifyPropertyValueUIItemsChanged();
		}

		/// <summary>
		/// Called when the <see cref="SerializationObject"/>'s property changes.
		/// </summary>
		/// <param name="sender">The sender.</param>
		/// <param name="e">The <see cref="System.ComponentModel.PropertyChangedEventArgs"/> instance containing the event data.</param>
		private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			var service = (IDesignerHost)GetService(typeof(IDesignerHost));

			if (service != null)
			{
				var transaction = service.CreateTransaction(String.Concat(e.PropertyName, " changed."));
				try
				{
					OnPropertyChanged(e);
					transaction.Commit();
				}
				catch(Exception ex)
				{
					transaction.Cancel();

					var designerService = (IFrameXmlDesignerService)GetService(typeof(IFrameXmlDesignerService));
					if (designerService != null)
						designerService.NotifyError(ex.Message);
				}
			}
            else
                OnPropertyChanged(e);
		}

        /// <summary>
        /// Called when the <see cref="SerializationObject"/>'s property changes.
        /// </summary>
        /// <param name="e">A <see cref="PropertyChangedEventArgs"/> that contains the event data.</param>
        protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            // TODO: Invalidate only when visible properties change
            this.Invalidate();
            this.SetDirtyFlag();
        }

    	/// <summary>
        /// Gets a service.
        /// </summary>
        /// <typeparam name="TService">The type of the service.</typeparam>
        /// <returns>An instance of the <typeparamref name="TService"/> class.</returns>
        protected TService GetService<TService>()
        {
            return (TService)this.GetService(typeof(TService));
        }

    	/// <summary>
		/// Refreshes the property browser.
		/// </summary>
		protected void RefreshPropertyBrowser()
		{
			var shell = GetService(typeof(SVsUIShell)) as IVsUIShell;
			if (shell != null)
			{
				shell.RefreshPropertyBrowser(0);
			}
		}

    	/// <summary>
		/// Updates the designer selection.
		/// </summary>
		protected void UpdateDesignerSelection()
		{
			try
			{
				//Get ISelectionService instance
				var service = (ISelectionService)GetService(typeof(ISelectionService));
				if (service != null)
				{
					//Get selected components in the designer
					ICollection selectedComponents = service.GetSelectedComponents();

					//Remove all selection in the designer
					service.SetSelectedComponents(new object[] { }, SelectionTypes.Primary);

					//Add selected components saved before
					service.SetSelectedComponents(selectedComponents, SelectionTypes.Primary);
				}
			}
			catch (Exception e)
			{
				Trace.WriteLine(e);
			}
		}

		/// <summary>
		/// Locks the control.
		/// </summary>
		/// <param name="locked">if set to <c>true</c> [locked].</param>
		protected void LockControl(bool locked)
		{
			LockControl(this, locked);
		}

		/// <summary>
		/// Locks the control.
		/// </summary>
		/// <param name="control">The control.</param>
		/// <param name="locked">if set to <c>true</c> [locked].</param>
		private void LockControl(IFrameXmlControl control, bool locked)
		{
			PropertyDescriptor property = TypeDescriptor.GetProperties(control)["Locked"];
			if (property != null)
			{
			    var layoutFrame = control.SerializationObject as LayoutFrame;
                if (layoutFrame != null)
                    layoutFrame.Locked = locked;
				
                property.SetValue(control, locked);
				RefreshPropertyBrowser();
			}
			foreach (Control ctrl in ((Control)control).Controls)
			{
				LockControl(ctrl as IFrameXmlControl, locked);
			}
		}

        /// <summary>
        /// Initializes the name.
        /// </summary>
        public void InitializeName()
        {
            var layoutFrame = SerializationObject as LayoutFrame;

            if (layoutFrame != null)
            {
                var expandedName = layoutFrame.Name;

                if (layoutFrame.Name.IsAvailable() && ((IFrameXmlControl)this).HasParent)
                {
                    // Expand the name of the LayoutFrame using the IFrameXmlControl parent
                    expandedName = ((IFrameXmlControl) this).Parent.ExpandName(layoutFrame);
                }

                //Get INameCreationService implementation
                var nameCreationService = GetService(typeof (INameCreationService)) as IFrameXmlNameCreationService;
                //Get the current container containing the control
                var container = GetService(typeof (IContainer)) as IContainer;

                if (nameCreationService != null)
                {
                    try
                    {
                        // Check whether the LayoutFrame has a valid name
                        if (layoutFrame.Name.IsAvailable())
                        {
                            //Check that given name is valid
                            if (!nameCreationService.HasValidName(this, layoutFrame.Name))
                                //Generate new valid name
                                Site.Name = Name = nameCreationService.CreateName(container, expandedName);
                            else
                                Site.Name = Name = expandedName;
                        }
                        else
                        {
                            // Generate a unique name for the layout frame
                            Site.Name = Name = nameCreationService.CreateName(container, SerializationObjectType);
                        }
                    }
                    catch (Exception e)
                    {
                        Trace.WriteLine(e);
                        // Generate a unique name for the layout frame
                        Site.Name =
                            Name =
                            nameCreationService.CreateName(container,
                                                           String.Format(CultureInfo.InvariantCulture, "{0}_{1:B}", expandedName,
                                                                         Guid.NewGuid()));
                    }
                }
            }
        }
    }
}
